package gott

import (
	"errors"
	"testing"
)

func divide5(by int) (int, error) {
	if by == 0 {
		return by, errors.New("divideByZero")
	} else {
		return 5 / by, nil
	}
}

func TestBindOk(t *testing.T) {
	r := R[int]{S: 5}.Bind(divide5)
	if r.E != nil {
		t.Errorf("error returned: %s\n", r.E)
	} else if r.S != 1 {
		t.Errorf("5 / 5 = %d, want 1\n", r.S)
	}
}

func TestBindErr(t *testing.T) {
	r := R[int]{S: 0}.Bind(divide5)
	if r.E == nil {
		t.Errorf("Error not returned\n")
	}
}

func add5(to int) int {
	return to + 5
}

func TestMap(t *testing.T) {
	r := R[int]{S: 0}.Map(add5)
	if r.E != nil {
		t.Errorf("Error returned: %s\n", r.E)
	} else if r.S != 5 {
		t.Errorf("0 + 5 = %d, want 5\n", r.S)
	}
}

func sideEffectWithError(int) error {
	return errors.New("Dummy error")
}

func TestTee(t *testing.T) {
	r := R[int]{S: 0}.Tee(sideEffectWithError)
	if r.E == nil {
		t.Errorf("Error not returned\n")
	}
}

func sideEffect(int) {
}

func TestSafeTee(t *testing.T) {
	r := R[int]{S: 1}.SafeTee(sideEffect)
	if r.E != nil {
		t.Errorf("Error returned\n")
	} else if r.S != 1 {
		t.Errorf("nop 0 = %d, want 0\n", r.S)
	}
}

var globalS int
var globalE error

func onSuccess(s int) {
	globalS = s
}

func onError(e error) {
	globalE = e
}

func TestHandleSuccess(t *testing.T) {
	globalS = 0
	globalE = nil
	r := R[int]{S: 5}.Handle(onSuccess, onError)
	if r.E != nil {
		t.Errorf("Error returned\n")
	} else if r.S != 5 {
		t.Errorf("Success modified\n")
	} else if globalS != 5 {
		t.Errorf("onSuccess not run\n")
	} else if globalE != nil {
		t.Errorf("onError run\n")
	}
}

func TestHandleError(t *testing.T) {
	globalS = 0
	globalE = nil
	r := R[int]{S: 5, E: errors.New("Dummy error")}.Handle(onSuccess, onError)
	if r.E == nil {
		t.Errorf("Error modified\n")
	} else if r.S != 5 {
		t.Errorf("Success modified\n")
	} else if globalS == 5 {
		t.Errorf("onSuccess run\n")
	} else if globalE == nil {
		t.Errorf("onError not run\n")
	}
}

func histeric(int) int {
	panic(42)
}

func TestCatchErr(t *testing.T) {
	exception := Exception{}
	r := R[int]{S: 0}.Catch(histeric)
	if errors.As(r.E, &exception) {
		if exception.E != 42 {
			t.Errorf("%s, want 42\n", r.E)
		}
	} else {
		t.Errorf("want Exception\n")
	}
}

func calm(int) int {
	return 5
}

func TestCatchNoErr(t *testing.T) {
	r := R[int]{S: 0}.Catch(calm)
	if r.E != nil {
		t.Errorf("error returned\n")
	}
	if r.S != 5 {
		t.Errorf("calm %d; want 5\n", r.S)
	}
}

func setZero(_ int, _ error) (int, error) {
	return 0, nil
}

func TestRecoverErr(t *testing.T) {
	r := R[int]{S: 0}.Bind(divide5).Recover(setZero)
	if r.E != nil {
		t.Errorf("Error returned\n")
	} else if r.S != 0 {
		t.Errorf("set %d; want 0\n", r.S)
	}
}

func TestRecoverNoErr(t *testing.T) {
	r := R[int]{S: 5}.Bind(divide5).Recover(setZero)
	if r.E != nil {
		t.Errorf("Error returned\n")
	} else if r.S != 1 {
		t.Errorf("set %d; want 1\n", r.S)
	}
}
